package de.riceman.simpleGo.players;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Random;

import javax.activation.UnsupportedDataTypeException;

import org.springframework.richclient.application.Application;

import de.riceman.simpleGo.events.SimpleGoLifecycleApplicationEvent;
import de.riceman.simpleGo.exceptions.IllegalMoveException;
import de.riceman.simpleGo.main.BitMatrix;
import de.riceman.simpleGo.main.Configuration;
import de.riceman.simpleGo.main.GoGame;
import de.riceman.simpleGo.main.Timer;


/**
 * An implementation of an automatic move generator implementing monte carlo tree search.
 * Real eyes won't be closed.
 * 
 * @author Mario Reissmann
 */
public class MonteCarloSimpleAI extends AI {
	
	/** Number of Monte Carlo Tree Searches per regular move. */
	private int iterations;
	
	
	/** Matrix to store the value of each move. */
	private TemperatureMatrix temperature;
	
	/** The Timer to measure the computing time. */
	private Timer timer = new Timer();
	
	/**
	 * Constructor for this class.
	 * 
	 * @param player this engine's player
	 * @param iterations this engine's number of iterations
	 */
	public MonteCarloSimpleAI(String name, boolean player, Configuration configuration, int strength) {
		super(name, player, configuration);

		iterations = strength;
	}
	
	/* (non-Javadoc)
	 * @see de.riceman.simpleGo.players.AI#generateMove()
	 */
	public void generateMove() {
		AbstractSet<BitMatrix> myGroups;
		BitMatrix eye;
		Iterator<BitMatrix> myGroupIt;
		Iterator<BitMatrix> eyeIt;
		
		temperature = new TemperatureMatrix(configuration.getSize());
		
		BitMatrix possibilities = configuration.getRegularMoves(player);
		
		//set all irregular moves to zero
		BitMatrix m = BitMatrix.not(possibilities);
		temperature.apply(m, 0.0);
		
		Application.instance().getApplicationContext().publishEvent(
				new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.AI_THINKS_OUT_LOUD, 
						"Temperature after eliminating irregular moves: \n" + temperature.toString() + "\n"));
		
		//remove all moves that would close an own real eye
		if (this.player) {
			myGroups = configuration.getDarkPartitions();
		} else {
			myGroups = configuration.getLightPartitions();
		}
		
		myGroupIt = myGroups.iterator();
		
		while (myGroupIt.hasNext()) {
			try {
				BitMatrix group = myGroupIt.next();
				
				eyeIt = group.getChildren().iterator();
						
				while (eyeIt.hasNext()) {
					eye = eyeIt.next();	
					eye.intersect(possibilities);
							
					if (eye.getNumberOfSetBits() <= 2) {
						possibilities.remove(eye);
					}
				}
			} catch (UnsupportedDataTypeException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		m = BitMatrix.not(possibilities);
		temperature.apply(m, 0.0);
		Application.instance().getApplicationContext().publishEvent(
				new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.AI_THINKS_OUT_LOUD, 
						"Temperature after removing moves into own small real eyes: \n" + temperature.toString() + "\n"));
		
		
		//do not do a move reducing an own group's liberties to one
		
		
		//perform simple monte carlo search
		Iterator<BitMatrix> it = possibilities.iterator();
		
		Configuration config;
		BitMatrix nextsPossibilities, group;
		
		boolean passed = false;
		int value = 0;
		long totalGamesPlayed = 0;
		
		timer.reset();
		timer.start();

		//for each possible move...
		while (it.hasNext()) {
			BitMatrix next = it.next();
			//System.out.println("starting with " + next.toString() + "\n");
			value = 0;
			
			for (int i = 0; i < iterations; i++) {
				//System.out.println("run " + Integer.toString(i));
				config = (Configuration) this.configuration.clone();
				try {
					config.doMove(this.player, next);
					//System.out.println("after trying move: " + next.toString());
					
					while(true) {

						nextsPossibilities = config.getRegularMoves(config.getPlayersTurn());		

						//Don't close your real eyes.
						if (config.getPlayersTurn()) {
							myGroups = config.getDarkPartitions();
						} else {
							myGroups = config.getLightPartitions();
						}
								
						myGroupIt = myGroups.iterator();
								
						while (myGroupIt.hasNext()) {
							try {
								group = myGroupIt.next();
								
								eyeIt = group.getChildren().iterator();
										
								while (eyeIt.hasNext()) {
									eye = eyeIt.next();	
									eye.intersect(nextsPossibilities);
											
									if (eye.getNumberOfSetBits() <= 2) {
										nextsPossibilities.remove(eye);
									}
								}
							} catch (UnsupportedDataTypeException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
						}
						
						if (!nextsPossibilities.isZeroMatrix()) {
							passed = false;
						} else {
								
							if (passed) {
									
								//Game finished.
								value += config.getConfigurationValue(this.player);
								totalGamesPlayed++;
								break;
									
							} else {
								passed = true;
							}
						}
						
						if (nextsPossibilities.isZeroMatrix()) {
							config.doMove(config.getPlayersTurn(), nextsPossibilities);
						} else {
							//do the almost random move
							BitMatrix move = nextsPossibilities.getRandomBitAsBitMatrix();
						
							while (move.isZeroMatrix()) {
								move = nextsPossibilities.getRandomBitAsBitMatrix();
							}
							config.doMove(config.getPlayersTurn(), move);
						}
						//timer.stop();
						//System.out.println(this.fieldToString(config.getDark(), config.getLight(), config.getSize()) + "\n\n");
						//timer.start();

					}
							
					//TODO: maybe implement more Go knowledge... ?!
	
				} catch (UnsupportedDataTypeException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalMoveException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				//free unused BitMatrices
				//config.dispose();
			}
			
			temperature.apply(next, value);
			timer.stop();
			Application.instance().getApplicationContext().publishEvent(
					new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.AI_THINKS_OUT_LOUD, 
							"possible move: " + next.toString() + " configuration value after " + Integer.toString(iterations)+ " games: " + Integer.toString(value) + "\n"));
			timer.start();
		}
		timer.stop();
		
		if ((timer.getElapsed() / 1000L) != 0L) {
			Application.instance().getApplicationContext().publishEvent(
					new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.AI_THINKS_OUT_LOUD, 
							"Monte Carlo tree search played " + Long.toString(totalGamesPlayed) + " games in " 
							+ timer.getTimeAfterRun() + " ms. That's " + totalGamesPlayed / (timer.getElapsed() / 1000L) + " per second."));
		}
		
		Application.instance().getApplicationContext().publishEvent(
				new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.AI_THINKS_OUT_LOUD, 
						"Temperature after monte carlo tree search: \n" + temperature.toString() + "\n"));

		//Check if your opponent has a group with only one liberty.
		AbstractSet<BitMatrix> opponentsGroups;
		if (this.player) {
			opponentsGroups = configuration.getLightPartitions();
		} else {
			opponentsGroups = configuration.getDarkPartitions();
		}
		
		it = opponentsGroups.iterator();
		
		while (it.hasNext()) {
			try {
				group = it.next();
				BitMatrix liberties = configuration.getLibertiesOfPartition(group);
				liberties.intersect(possibilities);
				
				if (liberties.getNumberOfSetBits() == 1) {
					temperature.multiply(liberties, 1.2);
					Application.instance().getApplicationContext().publishEvent(
							new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.AI_THINKS_OUT_LOUD, 
									"Pushing temperature of move taking a group's last remaining liberty: \n" + liberties.toString() + "\n"));
				}
			} catch (UnsupportedDataTypeException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		//Don't do any move that would leave one of your groups with only one liberty.
		it = temperature.getNonZeroValues().iterator();
		
		while (it.hasNext()) {
			group = it.next();
			
			try {
				if (!group.isZeroMatrix()) {
					config = (Configuration) configuration.clone();
					
						config.doMove(player, group);
					
					
					if (config.getLibertiesOfPartition(
							configuration.getResultingPartitionForMove(player, group))
							.getNumberOfSetBits() == 1) {
						temperature.multiply(group, 0.8);
						Application.instance().getApplicationContext().publishEvent(
								new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.AI_THINKS_OUT_LOUD, 
										"Inhibiting temperature of move resulting in a group with only one remaining liberty: \n" + group.toString() + "\n"));
					}
				}
			} catch (UnsupportedDataTypeException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalMoveException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		Application.instance().getApplicationContext().publishEvent(
				new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.AI_THINKS_OUT_LOUD, 
						"Temperature after pushing and inhibition: \n" + temperature.toString() + "\n"));
		
		
		//That's it.
		possibilities = selectRandomBitFromBitMatrix(temperature.getWinners());
		

		
		/*try {
			Thread.sleep(200);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}*/
		Application.instance().getApplicationContext().publishEvent(
				new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.AI_THINKS_OUT_LOUD, "\nCalculated the following move: \n" + possibilities.toString()));
		
		Application.instance().getApplicationContext().publishEvent(
				new SimpleGoLifecycleApplicationEvent(SimpleGoLifecycleApplicationEvent.MOVE_GENERATED, possibilities));
		
	}

	/**
	 * Select random bit from bit matrix.
	 * 
	 * @param a the a
	 * 
	 * @return the bit matrix
	 */
	public BitMatrix selectRandomBitFromBitMatrix (BitMatrix a) {
		BitMatrix move = new BitMatrix(a.getRows(), a.getColumns());
		
		int n = a.getNumberOfSetBits();
		
		if (n > 0) {
			
			Random rand = new Random();
		
			int randombit = rand.nextInt(n);
			//System.out.println("randombit=" + Integer.toString(randombit));
			n = 0;
			
			for (int i = 0; i < (a.getRows()*a.getColumns()); i++) {
				if (a.get(i)) {
					if (n == randombit) {
						move.set(i);
						break;
					}
					n++;
				}
			}
		}
		
		return move;
	}
	
/**
 * This method implements the minimax algorithm including alpha beta pruning.
 * 
 * @param board the chessboard
 * @param iterations remaining number of iterations
 * @param alpha alpha value
 * @param beta beta value
 * @return configuration value
 *//*
private int Minimax (GoView board, int iterations, int alpha, int beta) {

		return 0;
	}


	 (non-Javadoc)
	 * @see KI#getConfigurationValue(Board)
	 
	public int getConfigurationValue (GoView board) {

		return 0;
	}
	
	
	
	
	*//**
	 * This method calculates the material value of the board's configuration.
	 * 
	 * @param board the board
	 * @return the material value
	 *//*
	public int getMaterialValue(GoView board) {
		
		return 0;
	}*/

	@Override
	public int getConfigurationValue() {
		// TODO Auto-generated method stub
		return 0;
	}
	
	
	
	private String fieldToString(BitMatrix dark, BitMatrix light, int dimension) {
		StringBuffer out = new StringBuffer();
		
		//
		
		for (int i = 0; i < (dimension*dimension); i++) {

			if (i % dimension == 0) {
				out.append("\n");
			}
			
			if (dark.get(0, i)) {
				out.append("X|");
			} else if (light.get(0, i)) {
				out.append("O|");
			} else {
				out.append(" |");
			}
		}	
		
		return out.toString();
	}
}
